package Share;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.*;
import java.security.spec.X509EncodedKeySpec;
import java.util.Date;

import javax.crypto.Cipher;

public class Util {
	public static void write(DataOutputStream out, String string) throws IOException
	{
		byte[] data = string.getBytes("UTF-8");
		out.writeInt(data.length);
		out.write(data);
	}

	public static String readString(DataInputStream in) throws IOException
	{
		byte[] buffer = new byte[in.readInt()];
		in.read(buffer);
		return new String(buffer, "UTF-8");
	}
	
	public static void write(DataOutputStream out, byte[] data) throws IOException
	{
		out.writeInt(data.length);
		out.write(data);
	}
	
	public static byte[] readByteArray(DataInputStream in) throws IOException
	{
		int size = in.readInt();
		byte[] buffer = new byte[size];
		in.read(buffer);
		return buffer;
	}
	
	public static boolean compareByteArray(byte[] a, byte[] b)
	{
		if(a.length != b.length)
			return false;
		for(int i = 0; i < a.length; i++)
		{
			if(a[i] != b[i])
				return false;
		}
		return true;
	}
	
	public static KeyPair GetNewRSAKey()
	{
		try
		{
			KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
			keyGen.initialize(2048);
			return keyGen.genKeyPair();
		}
		catch(Exception e)
		{
			return null;
		}
	}
	
	public static boolean validateToken(Token token)
	{
		Date now = new Date();
		
		if(!Util.checkSig(token))
			return false;
		
		if(!token.certificate.owner.equals(token.signer))
			return false;
		
		if(token.expiration.before(now))
			return false;
		
		Token last = token;
		token = token.getNextToken();

		while(token != null)
		{
			switch(last.type)
			{
			case all:
				if(token.type != TokenType.all)
					return false;
				break;
			case put:
				if(token.type == TokenType.get)
					return false;
				break;
			case get:
				if(token.type == TokenType.put)
					return false;
				break;
			}

			if(!token.owner.equals(last.owner))
				return false;
			
			if(!token.deleg.equals( last.signer))
				return false;
			
			if(token.file != last.file)
				return false;
			
			if(!Util.checkSig(token))
				return false;
			
			if(!token.certificate.owner.equals(token.signer))
				return false;
			
			if(token.expiration.before(now))
				return false;
			
			last = token;
			token = token.getNextToken();
		}
		
		if(!last.signer.equals(last.owner))
			return false;
		
		return true;
	}
	
	public static boolean checkSig(byte[] data, byte[] signature, Certificate certificate)
	{
		try
		{
			PublicKey key = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(certificate.key));
			Cipher cipher = Cipher.getInstance("RSA");
			cipher.init(Cipher.DECRYPT_MODE, key);
			
			MessageDigest digest = MessageDigest.getInstance("SHA-512");
			byte[] hash = digest.digest(data);
			byte[] decrypt = cipher.doFinal(signature);
			return compareByteArray(hash, decrypt);
		}
		catch(Exception e)
		{
			return false;
		}
	}
	
	public static Boolean checkSig(Token token)
	{
		return verifyCertificate(token.certificate) &&
			checkSig(token.getDigest(), token.signature, token.certificate);
	}
	
	public static byte[] signData(byte[] data, PrivateKey key) throws SignatureException
	{
		try
		{
			MessageDigest digest = MessageDigest.getInstance("SHA-512");
			Cipher cipher = Cipher.getInstance("RSA");
			cipher.init(Cipher.ENCRYPT_MODE, key);
			return cipher.doFinal(digest.digest(data));
		}
		catch(Exception e)
		{
			throw new SignatureException("Unalbe to sign data", e);
		}
	}
	
	public static void signToken(Token token, PrivateKey key) throws SignatureException
	{
		token.signature = signData(token.getDigest(), key);
	}
	
	public static byte[] decrypt(byte[] data, PrivateKey key) throws CryptoException
	{
		try
		{
			Cipher cipher = Cipher.getInstance("RSA");
			cipher.init(Cipher.DECRYPT_MODE, key);
			return cipher.doFinal(data);
		}
		catch(Exception e)
		{
			throw new CryptoException("Unable to decrypt data", e);
		}
	}
	
	public static byte[] encryptData(byte[] data, Certificate certificate) throws CryptoException
	{
		try
		{
			PublicKey key = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(certificate.key));
			Cipher cipher = Cipher.getInstance("RSA");
			cipher.init(Cipher.ENCRYPT_MODE, key);
			return cipher.doFinal(data);
		}
		catch(Exception e)
		{
			throw new CryptoException("Unable to encrypt.", e);
		}
	}
	
	public static final String CA_NAME = "the_big_ca";
	
	private static final byte[] CA_KEY = new byte[] {
		  48,-126,   1,  34,  48,  13,   6,   9,  42,-122,  72,-122,  -9,  13,   1,   1,
		   1,   5,   0,   3,-126,   1,  15,   0,  48,-126,   1,  10,   2,-126,   1,   1,
		   0, -70,  29, -20,  -1,  98, -47,-111, -49,  98, 115, -51,  10,  73,  45, -54,
		 104,  70, -85,  -1,-105,  98,   5,  93, -90,  17,  71,  82,  -2,  33, -50, 120,
		 118, -20,  84,  48,  49, -38,  18, -25, 124,  55, -54, 126, 115,  -6,  29,  41,
		 107,  79, -88,  66,  -3, -61, 110, -89,  82,  78,  32, 118,  40,  57, -23,  11,
		-104,-118,-124, -74,  47,-110, -12, -77,  67, -99,  83, -87,  48, -41, 124, -73,
		  97, -62,  99, -42,  19,-128,  72,   8, -15, -96,  46,-120, 118, -60,  74,  27,
		  11, -73,  64, 105, -67, -14,  29,  74,  89, -66, -15,-120,  62, -21,   0,-112,
		 -35, -27,-126, -48,  32,  67, -14, -75,-117,  97,  54,  94,  20, 122, -76, 103,
		  76,  76,-112,-103,  -7,-110,-101, -64, 101,   8,-102,-105, -61, -60,  23, -97,
		 -83,  38, -81,  83, -67,-117,-118, 127, 105,  22, -59,  25,   8,  80,-113,-110,
		  93,  35, -42,  20,-106, -84,  15, -84,   5, -24, -34,  29, -23, -62,  98, -51,
		 -96,  12, -70,  98, -92,  58,  14,-110,  22,  14, 105, -85,-113, -19, -68, -48,
		 112, -68, 115,-101, 120, -19, -40,  14, -27,  15,-126,-119, -54,-121, -82, -71,
		  46,  12,-100,  95,  39, -63,  32,  73, 103, 120, -44, 119,   0, -43,-128, 117,
		  82, 116,  61, -66,-119, -98, -44,-112, -83, -57, -55, -68,  54,-128, -55,  12,
		 122,-119, -67,-101, -21,  77, -10,  36, -68,  96, -75,  32, 111, -20,  12,-100,
		 -99,   2,   3,   1,   0,   1
		};
	
	public static boolean verifyCertificate(Certificate cert)
	{
		try
		{
			if(!cert.signer.equals(CA_NAME))
				return false;
			
			PublicKey key = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(CA_KEY));
			Cipher cipher = Cipher.getInstance("RSA");
			cipher.init(Cipher.DECRYPT_MODE, key);
			byte[] decrypt = cipher.doFinal(cert.signature);
			byte[] hash = cert.hashForSig();
			return compareByteArray(decrypt, hash);
		} catch (Exception e)
		{
			return false;
		}
	}
}
